package je3.nio;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * BGrep: a regular expression search utility, like Unix grep, but
 * block-oriented instead of line-oriented.  For any match found, the
 * filename and character position within the file (note: not the line
 * number) are printed along with the text that matched.
 * <p/>
 * Usage:
 * java je3.nio.BGrep [options] <pattern> <files>...
 * <p/>
 * Options:
 * -e <encoding> specifies and encoding. UTF-8 is the default
 * -i enables case-insensitive matching.  Use -s also for non-ASCII text
 * -s enables strict (but slower) processing of non-ASCII characters
 * <p/>
 * This program requires that each file to be searched fits into main
 * memory, and so does not work with extremely large files.
 */
public class BGrep {
    public static void main(String[] args) {
        String encodingName = "UTF-8";  // Default to UTF-8 encoding
        int flags = Pattern.MULTILINE;  // Default regexp flags

        try { // Fatal exceptions are handled after this try block
            // First, process any options
            int nextarg = 0;
            while (args[nextarg].charAt(0) == '-') {
                String option = args[nextarg++];
                if (option.equals("-e")) {
                    encodingName = args[nextarg++];
                } else if (option.equals("-i")) {  // case-insensitive matching
                    flags |= Pattern.CASE_INSENSITIVE;
                } else if (option.equals("-s")) { // Strict Unicode processing
                    flags |= Pattern.UNICODE_CASE; // case-insensitive Unicode
                    flags |= Pattern.CANON_EQ;     // canonicalize Unicode
                } else {
                    System.err.println("Unknown option: " + option);
                    usage();
                }
            }

            // Get the Charset for converting bytes to chars
            Charset charset = Charset.forName(encodingName);

            // Next argument must be a regexp. Compile it to a Pattern object
            Pattern pattern = Pattern.compile(args[nextarg++], flags);

            // Require that at least one file is specified
            if (nextarg == args.length) usage();

            // Loop through each of the specified filenames
            while (nextarg < args.length) {
                String filename = args[nextarg++];
                CharBuffer chars;  // This will hold complete text of the file
                try {  // Handle per-file errors locally
                    // Open a FileChannel to the named file
                    FileInputStream stream = new FileInputStream(filename);
                    FileChannel f = stream.getChannel();

                    // Memory-map the file into one big ByteBuffer.  This is
                    // easy but may be somewhat inefficient for short files.
                    ByteBuffer bytes = f.map(FileChannel.MapMode.READ_ONLY,
                            0, f.size());

                    // We can close the file once it is is mapped into memory.
                    // Closing the stream closes the channel, too.
                    stream.close();

                    // Decode the entire ByteBuffer into one big CharBuffer
                    chars = charset.decode(bytes);
                }
                catch (IOException e) { // File not found or other problem
                    System.err.println(e);   // Print error message
                    continue;                // and move on to the next file
                }

                // This is the basic regexp loop for finding all matches in a
                // CharSequence. Note that CharBuffer implements CharSequence.
                // A Matcher holds state for a given Pattern and text.
                Matcher matcher = pattern.matcher(chars);
                while (matcher.find()) { // While there are more matches
                    // Print out details of the match
                    System.out.println(filename + ":" +       // file name
                            matcher.start() + ": " +  // character pos
                            matcher.group());      // matching text
                }
            }
        }
        // These are the things that can go wrong in the code above
        catch (UnsupportedCharsetException e) {    // Bad encoding name
            System.err.println("Unknown encoding: " + encodingName);
        }
        catch (PatternSyntaxException e) {         // Bad pattern
            System.err.println("Syntax error in search pattern:\n" +
                    e.getMessage());
        }
        catch (ArrayIndexOutOfBoundsException e) { // Wrong number of arguments
            usage();
        }
    }

    /**
     * A utility method to display invocation syntax and exit.
     */
    public static void usage() {
        System.err.println("Usage: java BGrep [-e <encoding>] [-i] [-s]" +
                " <pattern> <filename>...");
        System.exit(1);
    }
}
